THREE.OGLLoader = (function () {

    function OGLLoader(manager) {

        this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;

    }

    OGLLoader.prototype = {

        constructor: OGLLoader,

        crossOrigin: 'anonymous',

        load: function (url, onLoad, onProgress, onError) {

            var self = this;

            var path = (self.path === undefined) ? THREE.LoaderUtils.extractUrlBase(url) : self.path;

            var loader = new THREE.FileLoader(this.manager);
            loader.setPath(self.path);
            loader.setResponseType('arraybuffer');

            loader.load(url, function (buffer) {

                try {

                    onLoad(self.parse(buffer, path));

                } catch (error) {

                    setTimeout(function () {

                        if (onError) onError(error);

                        self.manager.itemError(url);

                    }, 0);

                }

            }, onProgress, onError);

        },

        setPath: function (value) {

            this.path = value;
            return this;

        },

        setResourcePath: function (value) {

            this.resourcePath = value;
            return this;

        },

        setCrossOrigin: function (value) {

            this.crossOrigin = value;
            return this;

        },

        parse: function (Buffer, path) {
            let bufferGeometry = OglParser.parseGeometry(Buffer);
            let materials = [];

            const colors = [
                new THREE.Color(99 / 255, 251 / 255, 251 / 255),
                new THREE.Color(70 / 255, 177 / 255, 177 / 255),
                new THREE.Color(139 / 255, 46 / 255, 233 / 255),
                new THREE.Color(255 / 255, 120 / 255, 255 / 255),
                new THREE.Color(255 / 255, 0 / 255, 143 / 255),
                new THREE.Color(183 / 255, 0 / 255, 183 / 255),
                new THREE.Color(255 / 255, 121 / 255, 60 / 255),
                new THREE.Color(255 / 255, 255 / 255, 121 / 255),
                new THREE.Color(180 / 255, 255 / 255, 59 / 255),
                new THREE.Color(255 / 255, 57 / 255, 57 / 255)
            ];

            for (let i = 0; i < bufferGeometry.groups.length; i++) {
				// 2019.11.19 yaoyuan
                //let material = new THREE.MeshBasicMaterial();
				let material = new THREE.MeshPhongMaterial();
                if (i < 9)
                    material.color = colors[i];
                else
                    material.color = colors[9];
                material.side = THREE.DoubleSide;
                materials.push(material);
            }

            let mesh = new THREE.Mesh(bufferGeometry, materials);
			
            let group = new THREE.Group();
            group.add(mesh);
            return group;
        }

    };

    var ArrayBufferReader = /** @class */function () {
        function ArrayBufferReader(buffer) {
            this.buffer = buffer;
            this.int32Array = new Uint32Array(buffer);
            this.offset = 0;
        }

        ArrayBufferReader.prototype.getUint32 = function () {
            var ret = this.int32Array[this.offset / 4]; //this is the trick for fetching the correct uint32 value
            this.offset += 4;
            return ret;
        };
        ArrayBufferReader.prototype.getUint16Array = function (len) {
            var ret = new Uint16Array(this.buffer, this.offset, len);
            this.offset += Uint16Array.BYTES_PER_ELEMENT * len;
            return ret;
        };
        ArrayBufferReader.prototype.getUint32Array = function (len) {
            var ret = new Uint32Array(this.buffer, this.offset, len);
            this.offset += Uint32Array.BYTES_PER_ELEMENT * len;
            return ret;
        };
        ArrayBufferReader.prototype.getFloat32Array = function (len) {
            var ret = new Float32Array(this.buffer, this.offset, len);
            this.offset += Float32Array.BYTES_PER_ELEMENT * len;
            return ret;
        };
        return ArrayBufferReader;
    }();

    //2018-9-4 wy add comments
    var OglParser = /** @class */ (function () {
        function OglParser() {
        }

        OglParser.parseGeometry = function (buffer, geometryId, extend) {
            var reader = new ArrayBufferReader(buffer);
            var geometry = new THREE.BufferGeometry();
            geometry.name = geometryId; //set geometryId to name for later use or debugging
            reader.getUint32();
            geometry.addAttribute('position', new THREE.BufferAttribute(reader.getFloat32Array(reader.getUint32()), 3));
            geometry.addAttribute('normal', new THREE.BufferAttribute(reader.getFloat32Array(reader.getUint32()), 3));
            var uvCount = reader.getUint32();
            uvCount && geometry.addAttribute('uv', new THREE.BufferAttribute(reader.getFloat32Array(uvCount), 2));
            var useInt16 = reader.getUint32() == 0, indexArray, indexLength = reader.getUint32();
            if (useInt16) {
                indexArray = reader.getUint16Array(indexLength);
            }
            else {
                indexArray = reader.getUint32Array(indexLength);
            }
            geometry.setIndex(new THREE.BufferAttribute(indexArray, 1));
            //补齐
            if (indexLength % 2 && useInt16) {
                reader.offset += 2;
            }
            if (extend) //set the extend from param if provided
                geometry.boundingBox = new THREE.Box3(extend.minimum, extend.maximum);
            var groupCount = reader.getUint32(); //at least one sub mesh
            // console.log("groupCount:" + groupCount);
            for (var i = 0; i < groupCount; i++) {
                var startIndex = reader.getUint32();
                var indexCount = reader.getUint32();
                var materialIndex = reader.getUint32();
                geometry.addGroup(startIndex, indexCount, materialIndex);
            }
            return geometry;
        };
        return OglParser;
    }());


    function BinaryParser() {
    }

    BinaryParser.prototype = {

        constructor: BinaryParser,

        parse: function (buffer) {

            var reader = new BinaryReader(buffer);
            reader.skip(23); // skip magic 23 bytes

            var version = reader.getUint32();

        },

        // Check if reader has reached the end of content.
        endOfContent: function (reader) {

            // footer size: 160bytes + 16-byte alignment padding
            // - 16bytes: magic
            // - padding til 16-byte alignment (at least 1byte?)
            //	(seems like some exporters embed fixed 15 or 16bytes?)
            // - 4bytes: magic
            // - 4bytes: version
            // - 120bytes: zero
            // - 16bytes: magic
            if (reader.size() % 16 === 0) {

                return ((reader.getOffset() + 160 + 16) & ~0xf) >= reader.size();

            } else {

                return reader.getOffset() + 160 + 16 >= reader.size();

            }

        },

        // recursively parse nodes until the end of the file is reached
        parseNode: function (reader, version) {

            var node = {};

            // The first three data sizes depends on version.
            var endOffset = (version >= 7500) ? reader.getUint64() : reader.getUint32();
            var numProperties = (version >= 7500) ? reader.getUint64() : reader.getUint32();

            // note: do not remove this even if you get a linter warning as it moves the buffer forward
            var propertyListLen = (version >= 7500) ? reader.getUint64() : reader.getUint32();

            var nameLen = reader.getUint8();
            var name = reader.getString(nameLen);

            // Regards this node as NULL-record if endOffset is zero
            if (endOffset === 0) return null;

            var propertyList = [];

            for (var i = 0; i < numProperties; i++) {

                propertyList.push(this.parseProperty(reader));

            }

            // Regards the first three elements in propertyList as id, attrName, and attrType
            var id = propertyList.length > 0 ? propertyList[0] : '';
            var attrName = propertyList.length > 1 ? propertyList[1] : '';
            var attrType = propertyList.length > 2 ? propertyList[2] : '';

            // check if this node represents just a single property
            // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
            node.singleProperty = (numProperties === 1 && reader.getOffset() === endOffset) ? true : false;

            while (endOffset > reader.getOffset()) {

                var subNode = this.parseNode(reader, version);

                if (subNode !== null) this.parseSubNode(name, node, subNode);

            }

            node.propertyList = propertyList; // raw property list used by parent

            if (typeof id === 'number') node.id = id;
            if (attrName !== '') node.attrName = attrName;
            if (attrType !== '') node.attrType = attrType;
            if (name !== '') node.name = name;

            return node;

        },

        parseSubNode: function (name, node, subNode) {

            // special case: child node is single property
            if (subNode.singleProperty === true) {

                var value = subNode.propertyList[0];

                if (Array.isArray(value)) {

                    node[subNode.name] = subNode;

                    subNode.a = value;

                } else {

                    node[subNode.name] = value;

                }

            } else if (name === 'Connections' && subNode.name === 'C') {

                var array = [];

                subNode.propertyList.forEach(function (property, i) {

                    // first Connection is FBX type (OO, OP, etc.). We'll discard these
                    if (i !== 0) array.push(property);

                });

                if (node.connections === undefined) {

                    node.connections = [];

                }

                node.connections.push(array);

            } else if (subNode.name === 'Properties70') {

                var keys = Object.keys(subNode);

                keys.forEach(function (key) {

                    node[key] = subNode[key];

                });

            } else if (name === 'Properties70' && subNode.name === 'P') {

                var innerPropName = subNode.propertyList[0];
                var innerPropType1 = subNode.propertyList[1];
                var innerPropType2 = subNode.propertyList[2];
                var innerPropFlag = subNode.propertyList[3];
                var innerPropValue;

                if (innerPropName.indexOf('Lcl ') === 0) innerPropName = innerPropName.replace('Lcl ', 'Lcl_');
                if (innerPropType1.indexOf('Lcl ') === 0) innerPropType1 = innerPropType1.replace('Lcl ', 'Lcl_');

                if (innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf('Lcl_') === 0) {

                    innerPropValue = [
                        subNode.propertyList[4],
                        subNode.propertyList[5],
                        subNode.propertyList[6]
                    ];

                } else {

                    innerPropValue = subNode.propertyList[4];

                }

                // this will be copied to parent, see above
                node[innerPropName] = {

                    'type': innerPropType1,
                    'type2': innerPropType2,
                    'flag': innerPropFlag,
                    'value': innerPropValue

                };

            } else if (node[subNode.name] === undefined) {

                if (typeof subNode.id === 'number') {

                    node[subNode.name] = {};
                    node[subNode.name][subNode.id] = subNode;

                } else {

                    node[subNode.name] = subNode;

                }

            } else {

                if (subNode.name === 'PoseNode') {

                    if (!Array.isArray(node[subNode.name])) {

                        node[subNode.name] = [node[subNode.name]];

                    }

                    node[subNode.name].push(subNode);

                } else if (node[subNode.name][subNode.id] === undefined) {

                    node[subNode.name][subNode.id] = subNode;

                }

            }

        },

        parseProperty: function (reader) {

            var type = reader.getString(1);

            switch (type) {

                case 'C':
                    return reader.getBoolean();

                case 'D':
                    return reader.getFloat64();

                case 'F':
                    return reader.getFloat32();

                case 'I':
                    return reader.getInt32();

                case 'L':
                    return reader.getInt64();

                case 'R':
                    var length = reader.getUint32();
                    return reader.getArrayBuffer(length);

                case 'S':
                    var length = reader.getUint32();
                    return reader.getString(length);

                case 'Y':
                    return reader.getInt16();

                case 'b':
                case 'c':
                case 'd':
                case 'f':
                case 'i':
                case 'l':

                    var arrayLength = reader.getUint32();
                    var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
                    var compressedLength = reader.getUint32();

                    if (encoding === 0) {

                        switch (type) {

                            case 'b':
                            case 'c':
                                return reader.getBooleanArray(arrayLength);

                            case 'd':
                                return reader.getFloat64Array(arrayLength);

                            case 'f':
                                return reader.getFloat32Array(arrayLength);

                            case 'i':
                                return reader.getInt32Array(arrayLength);

                            case 'l':
                                return reader.getInt64Array(arrayLength);

                        }

                    }

                    if (typeof Zlib === 'undefined') {

                        console.error('THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js');

                    }

                    var inflate = new Zlib.Inflate(new Uint8Array(reader.getArrayBuffer(compressedLength))); // eslint-disable-line no-undef
                    var reader2 = new BinaryReader(inflate.decompress().buffer);

                    switch (type) {

                        case 'b':
                        case 'c':
                            return reader2.getBooleanArray(arrayLength);

                        case 'd':
                            return reader2.getFloat64Array(arrayLength);

                        case 'f':
                            return reader2.getFloat32Array(arrayLength);

                        case 'i':
                            return reader2.getInt32Array(arrayLength);

                        case 'l':
                            return reader2.getInt64Array(arrayLength);

                    }

                default:
                    throw new Error('THREE.FBXLoader: Unknown property type ' + type);

            }

        }

    };

    function BinaryReader(buffer, littleEndian) {

        this.dv = new DataView(buffer);
        this.offset = 0;
        this.littleEndian = (littleEndian !== undefined) ? littleEndian : true;

    }

    BinaryReader.prototype = {

        constructor: BinaryReader,

        getOffset: function () {

            return this.offset;

        },

        size: function () {

            return this.dv.buffer.byteLength;

        },

        skip: function (length) {

            this.offset += length;

        },

        // seems like true/false representation depends on exporter.
        // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
        // then sees LSB.
        getBoolean: function () {

            return (this.getUint8() & 1) === 1;

        },

        getBooleanArray: function (size) {

            var a = [];

            for (var i = 0; i < size; i++) {

                a.push(this.getBoolean());

            }

            return a;

        },

        getUint8: function () {

            var value = this.dv.getUint8(this.offset);
            this.offset += 1;
            return value;

        },

        getInt16: function () {

            var value = this.dv.getInt16(this.offset, this.littleEndian);
            this.offset += 2;
            return value;

        },

        getInt32: function () {

            var value = this.dv.getInt32(this.offset, this.littleEndian);
            this.offset += 4;
            return value;

        },

        getInt32Array: function (size) {

            var a = [];

            for (var i = 0; i < size; i++) {

                a.push(this.getInt32());

            }

            return a;

        },

        getUint32: function () {

            var value = this.dv.getUint32(this.offset, this.littleEndian);
            this.offset += 4;
            return value;

        },

        // JavaScript doesn't support 64-bit integer so calculate this here
        // 1 << 32 will return 1 so using multiply operation instead here.
        // There's a possibility that this method returns wrong value if the value
        // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
        // TODO: safely handle 64-bit integer
        getInt64: function () {

            var low, high;

            if (this.littleEndian) {

                low = this.getUint32();
                high = this.getUint32();

            } else {

                high = this.getUint32();
                low = this.getUint32();

            }

            // calculate negative value
            if (high & 0x80000000) {

                high = ~high & 0xFFFFFFFF;
                low = ~low & 0xFFFFFFFF;

                if (low === 0xFFFFFFFF) high = (high + 1) & 0xFFFFFFFF;

                low = (low + 1) & 0xFFFFFFFF;

                return -(high * 0x100000000 + low);

            }

            return high * 0x100000000 + low;

        },

        getInt64Array: function (size) {

            var a = [];

            for (var i = 0; i < size; i++) {

                a.push(this.getInt64());

            }

            return a;

        },

        // Note: see getInt64() comment
        getUint64: function () {

            var low, high;

            if (this.littleEndian) {

                low = this.getUint32();
                high = this.getUint32();

            } else {

                high = this.getUint32();
                low = this.getUint32();

            }

            return high * 0x100000000 + low;

        },

        getFloat32: function () {

            var value = this.dv.getFloat32(this.offset, this.littleEndian);
            this.offset += 4;
            return value;

        },

        getFloat32Array: function (size) {

            var a = [];

            for (var i = 0; i < size; i++) {

                a.push(this.getFloat32());

            }

            return a;

        },

        getFloat64: function () {

            var value = this.dv.getFloat64(this.offset, this.littleEndian);
            this.offset += 8;
            return value;

        },

        getFloat64Array: function (size) {

            var a = [];

            for (var i = 0; i < size; i++) {

                a.push(this.getFloat64());

            }

            return a;

        },

        getArrayBuffer: function (size) {

            var value = this.dv.buffer.slice(this.offset, this.offset + size);
            this.offset += size;
            return value;

        },

        getString: function (size) {

            // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
            var a = [];

            for (var i = 0; i < size; i++) {

                a[i] = this.getUint8();

            }

            var nullByte = a.indexOf(0);
            if (nullByte >= 0) a = a.slice(0, nullByte);

            return THREE.LoaderUtils.decodeText(new Uint8Array(a));

        }

    };

    return OGLLoader;

})();
